home *** CD-ROM | disk | FTP | other *** search
- /*
- Copyright 2007-2009 Xmarks Inc.
-
- foxmarks-network.js: implements network interface to Syncd2.
-
- */
-
- const NS_ERROR_REDIRECT_LOOP = 2152398879;
- const NS_ERROR_CWD_ERROR = 0x804b0016;
-
- /*
-
- The Request class handles communication with a server. It is essentially
- an HTTP Javascript remote object broker, delivering objects from the client
- to the server and retrieving Javascript objects from the server in return.
- Request has built-in support for the Foxmarks authentication protocol,
- initiated by a 302 Redirect response.
-
- Arguments:
- method: a string indicating which HTTP method to use.
- url: either a string or an object containing any of
- protocol, host, and path. If the caller provides a string, that
- literal string is used as the target url. If an object is
- supplied, Request uses whatever components are provided to construct
- a final url, employing defaults for missing components.
- obj: the object to be transmitted to the server.
- opt: an object that contains optional information as described below
-
- Return value:
- Three different types of errors can occur in processing a request.
- 1) Network error: DNS failure, connection reset, etc.
- 2) Transport failure: HTTP 404, etc.
- 3) App failure: syncd rejects a request because of revision mismatch.
-
- If an error occurs, we return an integer status code in response.status.
-
-
- NEW opt parameter is a object/dict with optional values
- isAuthRequest - whether this is an authenticate request
- headers - any http headers that should be passed in
- ignoreBody - don't do any conversion of the body
- ignoreAuthTokens - don't update or check for authtokens
- noAuthDialog - authenticate but only if it can be done w/o UI
- noJSON - the data being returned isn't json so don't parse it
- */
-
-
- function Request(method, url, bodyobj, opt) {
- opt = opt || {};
- this._channel = null;
- this._streamLoader = null;
- this._callback = null;
- this._triedAuth = false;
- this._headers = opt.headers;
- this._ignoreBody = opt.ignoreBody;
- this._ignoreAuthTokens = opt.ignoreAuthTokens;
- this._noJSON = opt.noJSON;
- this._noAuthDialog = opt.noAuthDialog;
- try {
- this.JSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
- } catch(e){
- this.JSON = null;
- }
-
- // Argument url is either a string or an object
- // consisting of protocol, host, and path.
- // If it's a string, just take the string that's been
- // given. If it's an object, assemble the components
- // (applying defaults as appropriate) into a url.
-
- if (url instanceof Object) {
-
- // Set up defaults.
- if (!url.protocol) {
- if (Xmarks.gSettings.securityLevel == 1 ||
- (Xmarks.gSettings.securityLevel == 0 && opt.isAuthRequest)) {
- url.protocol = "https";
- } else {
- url.protocol = "http";
- }
- }
- if (!url.host) url.host = Xmarks.gSettings.host;
- if (!url.path) url.path = "/";
- if (url.path[0] != "/") url.path = "/" + url.path;
- this._url = url.protocol + "://" + url.host + url.path;
- } else {
- this._url = url;
- }
- this._bodyobj = bodyobj;
- this._method = method;
- this._isAuthRequest = opt.isAuthRequest;
- }
-
- Request.prototype = {
-
- /*
-
- Process a request. Given the protocol, host, and path, construct a
- url and execute the specified method against that url, inserting
- the given message body.
-
- When request completes, it calls the provided callback function,
- passing a single object (the "response object").
-
- */
-
- Start: function(callback) {
- Xmarks.LogWrite(">>> " + this._method + " " + this._StripPassword(this._url));
- if (this._bodyobj && !this._isAuthRequest) {
- Xmarks.LogWrite(">>> Body is: " +
- (!Xmarks.gSettings.getDebugOption("no-verbose") ?
- this._bodyobj.toJSONString() : "(disabled)"));
- }
- this._callback = callback;
-
- // Create a channel.
- var ios = Cc["@mozilla.org/network/io-service;1"].
- getService(Ci.nsIIOService);
- try {
- this._channel = ios.newChannelFromURI(ios.newURI(this._url,
- "UTF-8", null));
- } catch (e) {
- Xmarks.LogWrite("Couldn't create channel from " + this._url + ", error:" + e.toSource());
- callback(1008);
- return;
- }
-
- this._channel.QueryInterface(Ci.nsIUploadChannel);
- this._channel.loadFlags |=
- Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
-
- // If we have a body to transmit, set it up as an upload stream.
- if (this._bodyobj) {
- var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
- createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = "UTF-8";
- var stream = converter.convertToInputStream(
- this._bodyobj.toJSONString());
- this._channel.setUploadStream(stream, "application/json", -1);
- }
-
- // Special setup only for HTTP channels.
- if (this._channel instanceof Ci.nsIHttpChannel) {
-
- // Set the channel's method if necessary.
- if (this._method) {
- this._channel.requestMethod = this._method;
- }
-
- // Disable redirection.
- this._channel.redirectionLimit = 0;
- this._channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
-
- // Set the user agent.
- this._channel.setRequestHeader("User-Agent",
- this._channel.getRequestHeader("User-Agent") +
- " Xmarks-Fx/" + Xmarks.FoxmarksVersion(), false);
-
- if(!this._ignoreAuthTokens){
- var auth = Xmarks.gSettings.auth || "";
- if(auth.length > 0){
- this._channel.setRequestHeader("Authorization",
- "XMAuth " + auth, false);
- this._channel.setRequestHeader("X-Xmarks-Auth",
- auth, false);
- }
- }
-
- // Set other headers if provided.
- if (this._headers) {
- var self = this;
- forEach(this._headers, function(v, k) {
- self._channel.setRequestHeader(k, v, false);
- } );
- }
- }
-
- // Create a stream loader for retrieving the response.
- this._streamLoader = Cc["@mozilla.org/network/stream-loader;1"]
- .createInstance(Ci.nsIStreamLoader);
-
- // Fire it up. Note that this results in the channel's AsyncOpen
- // being called; if we have set an upload stream above, it will be
- // transmitted as part of the request. This is a bit strange, but
- // appears to be correct.
- try {
- // Before Firefox 3...
- this._streamLoader.init(this._channel, this, null);
- } catch(e) {
- // Firefox 3 style...
- this._streamLoader.init(this);
- this._channel.asyncOpen(this._streamLoader, null);
- }
- return;
- },
-
- Cancel: function() {
- if (this._channel && this._channel.isPending()){
- Xmarks.LogWrite("Cancelling Network Request");
- this._channel.cancel(0x804b0002);
- }
- },
-
- onStreamComplete: function(loader, ctxt, status, resultLength, result) {
- var response = {};
- if (Components.isSuccessCode(status)) {
- try {
- status = this._channel.responseStatus || 200;
- } catch (e) {
- this._callback( { status: 1005, errormsg:
- "Disable automatic proxy settings detection."} );
- return;
- }
-
- //try {
- // dump("cookie: " + this._channel.getResponseHeader("Set-Cookie") + "\n");
- // }catch(e){}
- var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
- createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = "utf-8";
- var msg = "";
- if (status == 200 || status == 201 || status == 204) {
- if(!this._ignoreBody){
- msg = converter.convertFromByteArray(result, resultLength);
- }
- if (msg && msg.length && !this._ignoreBody) {
- if(this._noJSON){
- response = {
- status: 0,
- txt: msg
- };
- } else {
- try {
- response = this.JSON ? this.JSON.decode(msg) :
- eval("(" + msg + ")");
- if(response.auth && response.auth.length > 0){
- Xmarks.gSettings.auth = response.auth;
- }
-
- if(response.username && response.username.toLowerCase() != Xmarks.gSettings.username.toLowerCase() && Xmarks.gSettings.username.indexOf('@') < 0 ){
- response = { status: 1010,
- errormsg: "Invalid server response" };
- Xmarks.LogWrite("CurrUser: " + Xmarks.gSettings.username);
- Xmarks.LogWrite("ServerUser: " + response.username);
- Xmarks.LogWrite("JSON Response: " + msg);
- }
- } catch(e) {
- Xmarks.LogWrite("Failed to parse message: " + msg);
- response = { status: 1010,
- errormsg: "Invalid server response" };
- }
- }
- }
- if (response.status == null) {
- response.status = 0;
- }
-
- // New Auth stuff
- if(response.status == 302){
- function RestartAfterAuth(response) {
- if (response.status == 0){
- Xmarks.gSettings.auth = response.auth;
- // We're authenticated. Retry original request.
- self.Start(self._callback);
- return;
- } else {
- // Auth failed. Return failure code.
- if (response.message == "Wrong username or password.") {
- response.status = 401;
- }
- self._callback(response);
- return;
- }
- }
-
- Xmarks.LogWrite(">>> Authenticating...");
-
- if (this._triedAuth) {
- Xmarks.LogWrite("In authentication loop. Possible proxy server caching issue.");
- this._callback( { status: 1012 } );
- return;
- }
-
- if (this._noAuthDialog) {
- if ((Xmarks.gSettings.rememberPassword &&
- Xmarks.gSettings.masterPasswordSet) ||
- (!Xmarks.gSettings.rememberPassword &&
- !Xmarks.gSettings.sessionPassword)) {
- Xmarks.LogWrite("Client skips authentication");
- this._callback( { status: 403 } );
- return;
- }
- }
-
- var location = response.authtoken_location;
- var self = this; // keep a handle on the original request
- // parse out the protocol, host, and path
- var colonslashslash = location.indexOf("://");
- var nextslash = location.indexOf("/", colonslashslash + 3);
- if (colonslashslash < 0 || nextslash < 0) {
- throw Error("Couldn't parse location string " + location);
- }
- var url = {}
- url.host = location.substr(colonslashslash + 3,
- nextslash - (colonslashslash + 3));
- url.host = Xmarks.gSettings.getCharPref('host-login', url.host);
-
- url.path = location.substr(nextslash);
- Xmarks.LogWrite("Requesting password.");
- try {
- var pw = Xmarks.gSettings.password;
- } catch (e) {
- Xmarks.LogWrite("User canceled password request.");
- this._callback(2);
- return;
- }
- var authReq = new Request(
- "POST",
- url,
- {
- username: Xmarks.gSettings.username,
- password: Base64.encode(pw)
- },
- {
- isAuthRequest: true
- }
- );
- this._triedAuth = true;
- authReq.Start(RestartAfterAuth);
- return;
- }
-
- } else {
- response = { status: status, "errormsg" : msg };
- }
- try { // Pass back the etag if there is one.
- response.etag = this._channel.getResponseHeader("Etag");
- } catch (e) {}
-
- if(Xmarks.gSettings.getDebugOption("no-verbose")){
- Xmarks.LogWrite(">>> Callback (disabled)");
- } else {
- var oldauth = response.auth;
- delete response.auth;
- if(this._noJSON){
- Xmarks.LogWrite(">>> Callback (not json, status: " + response.status + ")");
- } else {
- Xmarks.LogWrite(">>> Callback " + response.toSource());
- }
- response.auth = oldauth;
- }
- this._callback(response);
- return;
- } else {
- if (status == NS_ERROR_REDIRECT_LOOP && !this._isAuthRequest) {
- this._callback( { status: status });
- return;
- } else if (status == NS_ERROR_CWD_ERROR) {
- Xmarks.LogWrite("CWD error (mapping to 404)");
- this._callback( { status: 404 } );
- } else {
- Xmarks.LogWrite("network request failed; status is " +
- status.toString(16));
- this._callback( { status: status });
- }
- }
- },
-
- _StripPassword: function(url) {
- var exp = /^(.*):(.*)@(.*)$/
- var match = url.match(exp);
- return match ? match[1] + "@" + match[3] : url;
- }
- }
-
-